---
title: Creating Packages
description: A complete guide on how to create a package for Zoi.
---

This guide provides a start-to-finish walkthrough of creating a new package, testing it locally, and publishing it to the official Zoi package repositories for everyone to use.

## Understanding Zoi Repositories

Zoi organizes its packages into several repositories, each with a specific purpose. When you contribute a new package, you'll need to decide which repository is the best fit.

| Repository  | Description                                                                       |
| ----------- | --------------------------------------------------------------------------------- |
| `core`      | Essential packages and libraries; very common and well-maintained.                |
| `main`      | Important packages that don't fit in `core` but are essential for most users.     |
| `extra`     | New or niche packages; less common and may be less actively maintained.           |
| `community` | User-submitted packages. New entries start here and may graduate to higher tiers. |
| `test`      | Testing ground for new Zoi features and packages prior to release.                |
| `archive`   | Archived packages that are no longer maintained.                                  |

For your first contribution, you will almost always be adding your package to the **`community`** repository.

For more information about repositories [visit here](/docs/zds/zoi/repositories)

## Step 1: Creating Your Package File

The heart of every Zoi package is its `.pkg.lua` definition file. This is a Lua script that allows for dynamic logic, making it powerful for complex packages with variable URLs or platform-specific needs.

A `.pkg.lua` file defines a package by calling global functions like `package{}`, `install{}`, and `dependencies{}`. You have access to global variables like `PKG` (the package being defined) and `SYSTEM` (with `OS`, `ARCH`, `DISTRO`) to make your package definition dynamic.

Here is a basic example:

```lua
-- my-cli.pkg.lua

-- Use local variables for repeated values
local repo_owner = "user"
local repo_name = "my-cli"
local version = "1.2.3"

-- The main package definition table
package({
  name = repo_name,
  repo = "community",
  version = version,
  description = "A simple command-line utility.",
  website = "https://example.com/" .. repo_name,
  git = "https://github.com/" .. repo_owner .. "/" .. repo_name,
  maintainer = {
    name = "Your Name",
    email = "your.email@example.com",
    key = "DEADC0DEDEADBEEFDEADC0DEDEADBEEFDEADC0DE" -- GPG Key Fingerprint or URL
  },
  license = "MIT",
  tags = { "cli", "devtools" },
  bins = { "my-cli" }, -- Binaries this package provides
  conflicts = { "old-cli" } -- Packages this conflicts with
})

-- The installation methods
install({
  {
    name = "Binary",
    type = "binary",
    -- Use Lua to construct the URL dynamically
    url = "https://github.com/" .. repo_owner .. "/" .. repo_name .. "/releases/download/v" .. PKG.version .. "/" .. repo_name .. "-" .. SYSTEM.OS .. "-" .. SYSTEM.ARCH,
    platforms = { "all" }
  }
})

-- Dependencies
dependencies({
  build = {
    required = { "native:go" }
  }
})
```

## Step 2: Defining an Installation Method

The `install{}` block tells Zoi how to get the software. You can provide multiple methods, and Zoi will pick the best one. If you set `selectable = true`, the user will be prompted to choose.

```lua
install({
  selectable = true, -- Allows the user to choose between these methods
  {
    name = "Binary", -- A descriptive name for the method
    type = "binary",
    url = "...",
    platforms = { "linux-amd64", "macos-amd64", "windows-amd64" }
  },
  {
    name = "Build from Source",
    type = "source",
    url = PKG.git, -- Use the git URL from the package definition
    platforms = { "all" },
    -- Optional: build inside a Docker container for reproducibility.
    -- Use "hub:" for Docker Hub and "ghcr:" for GitHub Container Registry.
    docker_image = "hub:golang:1.21",
    build_commands = { "make" },
    -- After building, Zoi will look for an executable with the same name as the package.
    -- If the binary has a different name or is in a subdirectory, specify its path.
    -- When using docker_image, this path is inside the container.
    bin_path = "build/my-cli"
  }
})
```

For a full list of installation types (`binary`, `com_binary`, `source`, `script`) and their options, see the [Package Examples](/docs/zds/zoi/examples).

### Copying Additional Files

For `source` and `com_binary` installation types, you can specify additional files or directories to be copied from the build environment (or extracted archive) to the user's system. This is useful for installing things like documentation, shell completions, or other assets that are not part of the main binary.

This is done using the `files` field within an installation method:

```lua
install({
  {
    name = "Build from Source",
    type = "source",
    url = "...",
    build_commands = { "make" },
    bin_path = "build/my-cli",
    files = {
      {
        platforms = { "linux", "macos" },
        files = {
          { source = "man/my-cli.1", destination = "/usr/local/share/man/man1/my-cli.1" },
          { source = "completions/my-cli.bash", destination = "/usr/share/bash-completion/completions/my-cli" }
        }
      },
      {
        platforms = { "windows" },
        files = {
          { source = "docs/LICENSE", destination = "C:\ProgramData\my-cli\LICENSE" }
        }
      }
    }
  }
})
```

- The `files` field is a list of `FileGroup` tables.
- Each `FileGroup` has a `platforms` list to specify which OS it applies to.
- The `files` field inside the group is a list of `FileCopy` tables.
- Each `FileCopy` has a `source` (relative to the build/archive root) and a `destination` (an absolute path on the user's system). You can use `~/` in the destination to refer to the user's home directory.

### Security: Checksums and Signatures

It is **highly recommended** to include checksums and GPG signatures to verify downloads.

```lua
install({
  {
    type = "binary",
    url = "...",
    platforms = { "all" },
    -- URL to a checksums file (e.g. checksums.txt)
    checksums = release_base_url .. "/checksums.txt",
    -- GPG signature for the file
    sigs = {
      {
        file = "my-cli-" .. SYSTEM.OS .. "-" .. SYSTEM.ARCH,
        sig = release_base_url .. "/my-cli.sig"
      }
    }
  }
})
```

Zoi will use the `key` from the `maintainer` or `author` block to verify the signature. You can manage keys with the `zoi pgp` command.

## Step 3: Adding Dependencies

Define dependencies in the `dependencies{}` block.

```lua
dependencies({
  -- Build-time dependencies
  build = {
    required = { "native:make", "native:gcc" },
    optional = { "native:rust:for rust language support" }
  },
  -- Runtime dependencies
  runtime = {
    required = { "zoi:some-base-library" },
    -- Let the user choose a GUI provider
    options = {
      {
        name = "GUI",
        desc = "GUI Providers",
        all = false, -- 'false' means choose one, 'true' allows multiple
        depends = {
          "native:qt6:for KDE desktop environments",
          "native:gtk4:for GNOME-based desktop environments"
        }
      }
    }
  }
})
```

For more details, see the [Dependencies guide](/docs/zds/zoi/dependencies).

## Step 4: Advanced Features

Zoi supports many advanced features in the `package{}` block:

- `alt`: Redirect to another package, URL, or file.
- `updater`: Force `zoi update` to use a specific install method (e.g. `source`).
- `rollback = false`: Disable rollback backups for this package.
- `updates`: A list of messages (breaking changes, vulnerabilities) to show the user before installation.

See the [Package Examples](/docs/zds/zoi/examples) for how to use these.

## Step 5: Testing Your Package Locally

Before publishing, you **must** test your package locally.

```sh
# Install from your local .pkg.lua file
zoi install ./my-package.pkg.lua

# If testing a source build specifically
zoi build ./my-package.pkg.lua
```

After installation, run the package's command to ensure it works, then uninstall it to test cleanup.

```sh
zoi uninstall my-package
```

## Step 6: The `zoi package` Workflow (Advanced)

For more complex packages, Zoi provides a dedicated `package` command set for a structured build process.

1.  **`zoi package meta <path/to/file.pkg.lua>`**:
    Generates a `*.meta.json` file. This file resolves all the dynamic Lua script logic for each platform into a static description of all assets, checksums, and signatures. You can also specify `--type <type>` to force generation from a specific installation method (`binary`, `com_binary`, or `source`).

2.  **`zoi package build <path/to/file.meta.json>`**:
    Takes the `meta.json` file, downloads all the assets for the **current platform**, verifies them, and bundles them into a single Zoi package archive (`.pkg.tar.zst`).

3.  **`zoi package install <path/to/file.pkg.tar.zst>`**:
    Installs a package from a local Zoi package archive. This is how Zoi installs pre-built packages from a repository.

This workflow is used internally by Zoi's CI/CD to build and publish official packages, but you can also use it for your own complex build pipelines.

## Step 7: Publishing Your Package

Once your package is tested and ready, you can publish it to the official repositories or host your own.

For a complete guide on publishing, please see:

- **[Publishing Packages](/docs/zds/zoi/publishing-packages)**
